using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using UnityEngine;

namespace ExceptionModule
{
    class Comparer : IEqualityComparer<string>
    {
        public bool Equals(string x, string y)
        {
            return y.Contains(x);
        }

        public int GetHashCode(string obj)
        {
            return 0;
        }
    }
    public class LogConfiguration : ILogConfiguration
    {
        public const string UnknownAuthor = "Unknown-Author";
        private Dictionary<LogType, int> _globalLogType = new Dictionary<LogType, int>(5)
        {
            {LogType.Assert, LogHandleType.LogToConsole},
            {LogType.Log, LogHandleType.LogToConsole},
            {LogType.Warning, LogHandleType.LogToConsole},
            {LogType.Error, LogHandleType.LogToConsole},
            {LogType.Exception, LogHandleType.LogToConsole},
        };

        readonly struct ModuleLogConfiguration
        {
            public readonly string ModuleAuthor;
            public readonly Dictionary<LogType, int> Mapping;

            public ModuleLogConfiguration(string moduleAuthor, Dictionary<LogType, int> mapping)
            {
                ModuleAuthor = moduleAuthor;
                Mapping = mapping;
            }
        }

        private readonly Dictionary<string, ModuleLogConfiguration> _handlers;

        public LogConfiguration()
        {
            _handlers = new Dictionary<string, ModuleLogConfiguration>(new Comparer());
        }

        public void Add<TModule>([NotNull] string moduleAuthor)
        {
            Add<TModule>(moduleAuthor, LogHandleType.LogToConsole);
        }

        public void Add<TModule>(string moduleAuthor, int logHandleType)
        {
            Add<TModule>(moduleAuthor, CreateHandleMapping(logHandleType));
        }

        public void Add<TModule>(string moduleAuthor, Dictionary<LogType, int> handleMapping)
        {
            if (string.IsNullOrEmpty(moduleAuthor))
                throw new ArgumentException("Value cannot be null or empty.", nameof(moduleAuthor));

            var moduleType = typeof(TModule);
            var namespaceName = moduleType.Namespace;
            if (namespaceName == null)
                throw new Exception($"请把TModule（「{moduleType}）放在指定的namespace里");

            Add(namespaceName, moduleAuthor, handleMapping);
        }

        public void Add(string namespaceName, string moduleAuthor)
        {
            Add(namespaceName, moduleAuthor, LogHandleType.LogToConsole);
        }

        public void Add(string namespaceName, string moduleAuthor, int logHandleType)
        {
            Add(namespaceName, moduleAuthor, CreateHandleMapping(logHandleType));
        }

        public void Add([NotNull] string namespaceName, string moduleAuthor,
            [NotNull] Dictionary<LogType, int> handleMapping)
        {
            if (string.IsNullOrEmpty(namespaceName))
                throw new ArgumentException("Value cannot be null or empty.", nameof(namespaceName));
            if (string.IsNullOrEmpty(moduleAuthor))
                throw new ArgumentException("Value cannot be null or empty.", nameof(moduleAuthor));
            if (handleMapping == null) throw new ArgumentNullException(nameof(handleMapping));

            _handlers.Add(namespaceName, new ModuleLogConfiguration(moduleAuthor, handleMapping));
        }

        public Dictionary<LogType, int> GetLogHandlerType(MethodBase method)
        {
            var logConfiguration = GetLogConfiguration(method);
            if (logConfiguration.Mapping == null)
                return _globalLogType;

            var dic = new Dictionary<LogType, int>(logConfiguration.Mapping.Count);
            foreach (var pair in logConfiguration.Mapping)
            {
                var globalType = _globalLogType[pair.Key];
                dic.Add(pair.Key, pair.Value | globalType);
            }
            return dic;
        }

        public int GetLogHandlerType(MethodBase method, LogType logType)
        {
            var logConfiguration = GetLogConfiguration(method);
            var globalType = _globalLogType[logType];
            if (logConfiguration.Mapping == null)
                return globalType;

            if (!logConfiguration.Mapping.TryGetValue(logType, out var handleType))
                return globalType;
            
            return globalType | handleType;
        }

        public string GetModuleAuthor(MethodBase method)
        {
            var logConfiguration = GetLogConfiguration(method);
            if (string.IsNullOrEmpty(logConfiguration.ModuleAuthor))
                return UnknownAuthor;

            return logConfiguration.ModuleAuthor;
        }

        public void AddGlobalConfiguration(LogType logType, int logHandleType)
        {
            _globalLogType[logType] = logHandleType;
        }

        private ModuleLogConfiguration GetLogConfiguration(MethodBase method)
        {
            var namespaceName = GetMethodNamespace(method);
            return GetLogConfiguration(namespaceName);
        }

        private ModuleLogConfiguration GetLogConfiguration(string namespaceName)
        {
            if (string.IsNullOrEmpty(namespaceName))
                return default;
            if (!_handlers.TryGetValue(namespaceName, out var logConfiguration))
                return default;

            return logConfiguration;
        }

        private static string GetMethodNamespace(MethodBase method)
        {
            return method.DeclaringType.Namespace;
        }

        private static Dictionary<LogType, int> CreateHandleMapping(int logHandleType)
        {
            return new Dictionary<LogType, int>(5)
            {
                {LogType.Assert, logHandleType},
                {LogType.Log, logHandleType},
                {LogType.Warning, logHandleType},
                {LogType.Error, logHandleType},
                {LogType.Exception, logHandleType},
            };
        }
    }
}